package tk.mynoteapp.dev.demo08.intandstringequal;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 注意 equals 和 == 的区别
 *   - == 是比较的是直接值，基本类型只能用==，而引用类型使用==比较的直接值是指针
 *   - equals 用于引用类型内容的比较
 */
@RestController
@RequestMapping("intandstringequal")
public class IntAndStringEqualController {
    private static final Logger logger = LoggerFactory.getLogger(IntAndStringEqualController.class);

    @GetMapping("intcompare")
    public void intcompare() {
        // 案例一
        // 编译器会把 Integer a = 127 转换为 Integer.valueOf(127)
        // 转换在内部其实做了缓存(IntegerCache)，使得两个 Integer 指向同一个对象
        Integer a = 127; // Integer.valueOf(127)
        Integer b = 127; // Integer.valueOf(127)
        logger.info("\nInteger a = 127;\n" +
                "Integer b = 127;\n" +
                "a == b ? {}", a == b); // true

        // 案例二
        // 默认情况下会缓存[-128, 127]的数值，而 128 处于这个区间之外。
        // 设置 JVM 参数加上 -XX:AutoBoxCacheMax=1000
        Integer c = 128; // Integer.valueOf(128)
        Integer d = 128; // Integer.valueOf(128)
        logger.info("\nInteger c = 128;\n" +
                "Integer d = 128;\n" +
                "c == d ? {}", c == d); // false

        // 案例三
        // New 出来的 Integer 始终是不走缓存的新对象。比较两个新对象，
        // 或者比较一个新对象和一个来自缓存的对象，结果肯定不是相同的对象，因此返回 false
        Integer e = 127; // Integer.valueOf(127)
        Integer f = new Integer(127); // new instance
        logger.info("\nInteger e = 127;\n" +
                "Integer f = new Integer(127);\n" +
                "e == f ? {}", e == f); // false

        // 案例四
        // New 的不走缓存
        Integer g = new Integer(127); // Integer.valueOf(127)
        Integer h = new Integer(127); // new instance
        logger.info("\nInteger g = new Integer(127);\n" +
                "Integer h = new Integer(127);\n" +
                "g == h ? {}", g == h); // false

        // 案例四
        // 把装箱的 Integer 和基本类型 int 比较，前者会先拆箱再比较，比较的肯定是数值而不是引用，因此返回 true。
        Integer i = 128; // unbox
        int j = 128;
        logger.info("\nInteger i = 128;\n" +
                "int j = 128;\n" +
                "i == j ? {}", i == j); // true
    }

    // 因为StatusEnum和OrderQuery的status字段都是Integer包装类型最后使用equals进行判断
    // 超过127的整数不会被自动缓存，如果==比较，状态开始小于127，后面大于127，程序将出现bug
    @PostMapping("enumcompare")
    public void enumcompare(@RequestBody OrderQuery orderQuery) {
        StatusEnum statusEnum = StatusEnum.DELIVERED;
        // 错误
        logger.info("wrong orderQuery:{} statusEnum:{} result: {}",
                orderQuery, statusEnum, statusEnum.status == orderQuery.getStatus());
        // 正确
        logger.info("right orderQuery:{} statusEnum:{} result: {}",
                orderQuery, statusEnum, statusEnum.status.equals(orderQuery.getStatus()));
    }

    enum StatusEnum {
        CREATED(1000, "已创建"),
        PAID(1001, "已支付"),
        DELIVERED(1002, "已送到"),
        FINISHED(1003, "已完成"),
        ;

        private final Integer status;
        private final String desc;

        StatusEnum(Integer status, String desc) {
            this.status = status;
            this.desc = desc;
        }
    }

    /**
     * Java 字符串常量池机制：
     *   目的：节省内存
     *
     */
    @GetMapping("stringcompare")
    public void stringcompare() {
        // 案例一
        // 第一个案例返回 true，因为 Java 的字符串驻留机制，直接使用双引号声明出来的两个
        // String 对象指向常量池中的相同字符串。
        String a = "1";
        String b = "1";
        logger.info("\nString a = \"1\";\n" +
                "String b = \"1\";\n" +
                "a == b ? {}", a == b); // true

        // 案例二
        // 第二个案例，new 出来的两个 String 是不同对象，引用当然不同，所以得到 false 的结果。
        String c = new String("2");
        String d = new String("2");
        logger.info("\nString c = new String(\"2\");\n" +
                "String d = new String(\"2\");\n" +
                "c == d ? {}", c == d); // false

        // 案例三
        // 第三个案例，使用 String 提供的 intern 方法也会走常量池机制，所以同样能得到 true。
        // 虽然使用 new 声明的字符串调用 intern 方法，也可以让字符串进行驻留，
        // 但在业务代码中滥用 intern，可能会产生性能问题
        String e = new String("3").intern();
        String f = new String("3").intern();
        logger.info("\nString e = new String(\"3\").intern();\n" +
                "String f = new String(\"3\").intern();\n" +
                "e == f ? {}", e == f); // true

        // 案例四
        // 第四个案例，通过 equals 对值内容判等，是正确的处理方式，当然会得到 true。
        String g = new String("4");
        String h = new String("4");
        logger.info("\nString g = new String(\"4\");\n" +
                "String h = new String(\"4\");\n" +
                "g == h ? {}", g.equals(h)); // true
    }

    List<String> list = new ArrayList<>();

    // 第二原则：没事别轻易用 intern，如果要用一定要注意控制驻留的字符串的数量，并留意常量表的各项指标
    @GetMapping("internperformance")
    public int internperformance(@RequestParam(value = "size", defaultValue = "10000000") int size) {
        //-XX:+PrintStringTableStatistics：JVM 参数 -XX:+PrintStringTableStatistic，程序退出时可以打印出字符串常量表的统计信息

        // 解决方式是，设置 JVM 参数 -XX:StringTableSize，指定更多的桶。
        // 设置 -XX:StringTableSize=10000000 后，重启应用
        //-XX:StringTableSize=10000000
        long begin= System.currentTimeMillis();
        list = IntStream.rangeClosed(1, size)
                .mapToObj(i -> String.valueOf(i).intern())
                .collect(Collectors.toList());
        logger.info("size:{} took:{}", size, System.currentTimeMillis() - begin);
        return list.size();
    }
}
